﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Contracts.Exceptions;
using System.Reflection;
using TypeResolve;

namespace XcoAppSpaces.Core.Configuration
{
	/// <summary>
	/// The SpaceStringConfigurator class allows to configure an appspace instance using a config string.
	/// </summary>
	public class SpaceStringConfigurator
	{
		private static List<Assembly> currentAssemblies;

		/// <summary>
		/// Creates an XcoAppSpaceConfiguration object from the given config string, which can then be used to create a
		/// new appspace instance with the given configuration. An XcoConfigurationException is thrown if the config 
		/// string is invalid.
		/// </summary>
		/// <param name="configString">The config string for which the configuration object should be created.</param>
		/// <returns>The configuration that was created from the config string.</returns>
		public static XcoAppSpaceConfig CreateConfig(string configString)
		{
#if SILVERLIGHT
			LoadCurrentAssemblies(); //for silverlight, load assemblies in any case, since they will be needed later, where they may not be loadable in another context
#endif

			var config = new XcoAppSpaceConfig();
			var configValues = SplitConfigString(configString);
			lock (DefaultInterpreters.CustomConfigStringInterpreterTypes)
			{
				var interpreters = DefaultInterpreters.ConfigStringInterpreters.ToList();
				TryLoadAdditionalInterpretersIfNeeded(interpreters, configValues);
				InterpretValuesAndAddToConfig(interpreters, configValues, config);
			}
			return config;
		}

		private static Dictionary<string, string> SplitConfigString(string configString)
		{
			var configValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			string[] configElements = configString.Split(';');
			foreach (string configElement in configElements.Where(c => c.Length > 0))
			{
				string[] elementParts = configElement.Split('=');
				if (elementParts.Length != 2 || elementParts[0].Length == 0 || elementParts[1].Length == 0)
					throw new XcoConfigurationException("Configuration string element invalid: \"" + configElement + "\"");
				configValues.Add(elementParts[0], elementParts[1]);
			}
			return configValues;
		}

		private static void TryLoadAdditionalInterpretersIfNeeded(List<IXcoConfigStringInterpreter> alreadyLoadedInterpreters, Dictionary<string, string> configValues)
		{
			if (UnknownConfigKeysExist(alreadyLoadedInterpreters, configValues))
			{
				var customInterpreterTypes = LoadCustomConfigStringInterpreterTypes().ToList();
				DefaultInterpreters.CustomConfigStringInterpreterTypes.AddRange(customInterpreterTypes);
				alreadyLoadedInterpreters.AddRange(customInterpreterTypes.Select(t => (IXcoConfigStringInterpreter)Activator.CreateInstance(t)));
			}
		}

		private static bool UnknownConfigKeysExist(IEnumerable<IXcoConfigStringInterpreter> alreadyLoadedInterpreters, Dictionary<string, string> configValues)
		{
			var knownConfigValues = alreadyLoadedInterpreters.SelectMany(i => i.InterpretKeys).ToList();
			return configValues.Keys.Any(c => !knownConfigValues.Contains(c));
		}

		private static void InterpretValuesAndAddToConfig(IEnumerable<IXcoConfigStringInterpreter> interpreters, Dictionary<string, string> configValues, XcoAppSpaceConfig config)
		{
			foreach (var serviceConfig in interpreters
				.Select(i => i.Interpret(configValues))
				.Where(s => s.Service != null))
			{
				config.AddService(serviceConfig.Service, serviceConfig.Name, serviceConfig.IsDefault);
			}
		}

		internal static IEnumerable<Type> LoadCustomConfigStringInterpreterTypes()
		{
			var alreadyLoadedInterpreterTypes = DefaultInterpreters.GetDefaultConfigStringInterpreters().Select(i => i.GetType())
				.Union(DefaultInterpreters.CustomConfigStringInterpreterTypes).ToList();
			foreach (var type in GetCurrentAssemblies()
				.Where(a => !IsFrameworkAssembly(a))
				.SelectMany(a => a.DefinedTypes.Where(t => 
					t.IsClass && !t.IsAbstract &&
					t.ImplementedInterfaces.Contains(typeof (IXcoConfigStringInterpreter)) &&
					t.DeclaredConstructors.Any(c => !c.GetParameters().Any()))
				.Select(t => t.AsType())
				.Where(t => !alreadyLoadedInterpreterTypes.Contains(t))))
			{
				yield return type;
			}
		}

		private static IEnumerable<Assembly> GetCurrentAssemblies()
		{
			if (currentAssemblies == null)
				LoadCurrentAssemblies();
			return currentAssemblies;
		}
		
		private static void LoadCurrentAssemblies()
		{
			currentAssemblies = TypeUtils.LoadCurrentAssemblies().ToList();
		}

		private static bool IsFrameworkAssembly(Assembly assembly)
		{
			var name = assembly.ManifestModule.Name;
//#if !PocketPC && !SILVERLIGHT
//			return (name.StartsWith("mscorlib") || name.StartsWith("System.")) && assembly.GlobalAssemblyCache;
//#else
			return (name.StartsWith("mscorlib") || name.StartsWith("System."));
//#endif
		}
	}
}
